//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <kernio/io.h>
#include <process/common.h>

// Prototypes

// reads the current EIP into a variable
uint32_t readeip();

// Functions

// performs a context switch
void ctxswitch( void** old, void* new, uint32_t cr3 );

// selects a task to execute and switches contexts
void kSchedule( uint32_t UNUSED(reserved) )
{
	// get the cpu flags
	uint32_t int_state = 0;
	asm volatile( "pushf; popl %0" : "=r" (int_state) );

	// clear the interrupt flag while we work with the queues
	asm volatile( "cli" );

	// first check that there are ready processes
	if(p_readyq == 0)
	{
		if(p_alreadyq == 0)
		{
			// umm... is this really fatal? because we kind of just return...
			dprintf("[FATAL] No valid process queues!\n");
			if(int_state & 0x200)
				asm volatile("sti");
			return; // can't schedule
		}
		else
		{
			p_readyq = p_alreadyq;
			p_alreadyq = 0; // no more processes on the already queue
		}
	}

	// take the first process off the ready queue
	struct pentry* ent = p_readyq;
	p_readyq = ent->next;
	if(p_readyq)
		p_readyq->prev = 0;

	// link this into the already queue
	if(p_alreadyq == 0)
	{
		p_alreadyq = ent;
		p_alreadyq->prev = p_alreadyq->next = 0;
	}
	else
	{
		p_alreadyq->prev = ent;
		ent->prev = 0;
		ent->next = p_alreadyq;
		p_alreadyq = ent;
	}

	// save the current task
	static struct context* oldctx = 0;
	oldctx = currprocess;

	// setup the new task
    currprocess = ent->me;
    currprocess->status = PSTATE_RUNNING;
    currprocess->timeslice = currprocess->priority * 5; // so 10 priority runs for 50 ms

    // grab ESP and EBP for saving into the old context
    uint32_t esp, ebp;
    asm volatile( "mov %%esp, %0" : "=r" (esp) );
    asm volatile( "mov %%ebp, %0" : "=r" (ebp) );

    // read in the EIP for the next task to return to
    uint32_t reteip = readeip();

	// done with tables and global pointers
	if(int_state & 0x200)
		asm volatile("sti");

    // have we just returned from a task?
    if( reteip == 0x12345 )
    {
		return; // yes, jump to it
    }

	// save registers
	if( oldctx )
	{
		oldctx->esp = esp;
		oldctx->ebp = ebp;
		oldctx->eip = reteip;
	}

	// and then load from the new task
	esp = currprocess->esp;
	ebp = currprocess->ebp;
	reteip = currprocess->eip;

	asm volatile(	"cli; \
					mov %0, %%ecx; \
					mov %1, %%esp; \
					mov %2, %%ebp; \
					mov %3, %%cr3; \
					mov $0x12345, %%eax; \
					sti; \
					jmp *%%ecx;"
					:
					:
					"r" (reteip), "r" (esp), "r" (ebp), "r" (currprocess->cr3)
				);
}
